package com.ingenico.insider.nodes;

import java.awt.BasicStroke;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;

import com.ingenico.insider.services.impl.AsynchronousSamplingJobProcessor;
import com.ingenico.insider.services.impl.AsynchronousSamplingJobProcessor.SamplingJob;
import com.ingenico.insider.services.impl.AsynchronousSamplingJobProcessor.SamplingJobOutput;
import com.ingenico.piccolo.event.CameraModificationEvent;
import com.ingenico.piccolo.event.CameraModificationListener;
import com.ingenico.tools.data.sampling.PathSampler;
import com.ingenico.tools.nio.channel.RandomReadableSampleChannel;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.activities.PActivity;
import edu.umd.cs.piccolo.util.PAffineTransform;
import edu.umd.cs.piccolo.util.PBounds;

public class PThreadedChoppingNode extends PCurve implements CameraModificationListener {
	private static final Logger _logger = Logger.getLogger(PThreadedChoppingNode.class.getCanonicalName()); 

	/**
	 * Generated Serial Version UID
	 */
	private static final long serialVersionUID = -2476390267683296552L;

	private PBounds oldViewBounds;
	private PBounds oldBounds;
	private PAffineTransform oldViewTransform;

	private final RandomReadableSampleChannel dataChannel;

	protected AsynchronousSamplingJobProcessor samplingThread;

	public void initActivity (PBounds newViewBounds, PBounds newBounds, PAffineTransform newViewTransform) {
		addActivity(new PActivity(-1) {
			@Override
			protected void activityStep(long elapsedTime) {
				super.activityStep(elapsedTime);
				if (samplingThread.isJobOutputAvailable(PThreadedChoppingNode.this)) {
					SamplingJobOutput jobOutput = samplingThread.getJobOutput(PThreadedChoppingNode.this); 
					Path2D path = jobOutput.getPath();
					PCurvePath chunk = new PCurvePath();
					chunk.setStroke(new BasicStroke(0.001f));
					chunk.setPathReference(path);
					chunk.setStrokePaint(strokePaint);
					_logger.fine("Adding node " + chunk);
					addChild(chunk);
				}
			}
		});

		// When we init activity, we also set the current bounds
		geometryChanged(newViewBounds, newBounds, newViewTransform);
	}

	public PThreadedChoppingNode (RandomReadableSampleChannel dataChannel) {
		this.dataChannel = dataChannel;
	}

	@Override
	public void addChild(PNode child) {
		int childrenCount = getChildrenCount();
// FIXME: the max number of children should be configurable as well as the max chunk number of the chopper...
		if (childrenCount > 20) {
			removeChild(0);
		}

		super.addChild(child);
	}

	@Override
	public void setPathSampler(PathSampler sampler) {
		super.setPathSampler(sampler);

		PBounds newViewBounds = oldViewBounds;
		PBounds newBounds = oldBounds;
		PAffineTransform newViewTransform = oldViewTransform;

		oldViewBounds = null;
		oldBounds = null;
		oldViewTransform = null;
//FIXME: bounds are broken here... we should maintain them
		samplingThread.cancelAllJobs(this);
		samplingThread.clearAllJobResults(this);
		removeAllChildren();
		if ((newViewBounds != null) && (newBounds != null) && (newViewTransform != null) ) {
			geometryChanged(newViewBounds, newBounds, newViewTransform);
		}
	}

	@Override
	public void positionChange(CameraModificationEvent e) {
		final PCamera sourceCamera = (PCamera)e.getSource();
		
		final PBounds newBounds     = sourceCamera.getBounds();
		final PBounds newViewBounds = sourceCamera.getViewBounds();
		final PAffineTransform newViewTransform = sourceCamera.getViewTransform();
		
		geometryChanged(newViewBounds, newBounds, newViewTransform);
	}

	@Override
	public void scaleChange(CameraModificationEvent e) {
		final PCamera sourceCamera = (PCamera)e.getSource();

		final PBounds newBounds     = sourceCamera.getBounds();
		final PBounds newViewBounds = sourceCamera.getViewBounds();
		final PAffineTransform newViewTransform = sourceCamera.getViewTransform();

		samplingThread.cancelAllJobs(this);
		samplingThread.clearAllJobResults(this);
		removeAllChildren();
		geometryChanged(newViewBounds, newBounds, newViewTransform);
	}

	@Override
	public void sizeChange(CameraModificationEvent e) {
		final PCamera sourceCamera = (PCamera)e.getSource();
		
		final PBounds newBounds     = sourceCamera.getBounds();
		final PBounds newViewBounds = sourceCamera.getViewBounds();
		final PAffineTransform newViewTransform = sourceCamera.getViewTransform();

		samplingThread.cancelAllJobs(this);
		samplingThread.clearAllJobResults(this);
		removeAllChildren();
		geometryChanged(newViewBounds, newBounds, newViewTransform);
	}

	private void geometryChanged (PBounds newViewBounds, PBounds newBounds, PAffineTransform newViewTransform) {
		final double newScale = newViewTransform.getScaleX();

		// Translates coordinates to global coordinates
		_logger.finer("GeometryChanged, parent node = " + getParent().toString());
		_logger.finer("GlobalViewBounds = " + newViewBounds);
		globalToLocal(newViewBounds);
		_logger.finer("LocalViewBounds = " + newViewBounds);

		final int    canvasWidth  = (int)newBounds.getWidth();
		Set<Rectangle2D> chunkSet = chopper.getChunkSet(newViewBounds, canvasWidth);
		Set<Rectangle2D> commonChunks = new HashSet<Rectangle2D>();

		Iterator<Rectangle2D> chunkIterator = chunkSet.iterator(); 
		while (chunkIterator.hasNext()) {
			final Rectangle2D currentChunk = (Rectangle2D) chunkIterator.next();
			_logger.finer("Current chunk bounds = " + currentChunk);
			Rectangle2D intersection;
			final Iterator<PNode> childrenIterator = getChildrenIterator();
			while (childrenIterator.hasNext()) {
				final PNode childNode = (PNode) childrenIterator.next();
				final Rectangle2D childBounds = childNode.getBounds();
				_logger.finer("Current child bounds = " + childBounds);

				intersection = childBounds.createIntersection(currentChunk);
				_logger.finer("Intersection between child and chunck = " + intersection);

				if (intersection.getWidth() > (currentChunk.getWidth()/2)) {
					_logger.finer("Intersection is large and will not be added");
					commonChunks.add(currentChunk);
				}
			}
		}

		// Remove common chunks and iterate over the remaining chunks to add them
		chunkSet.removeAll(commonChunks);
		chunkIterator = chunkSet.iterator(); 
		while (chunkIterator.hasNext()) {
			final Rectangle2D currentChunk = (Rectangle2D) chunkIterator.next();

			SamplingJob job = new SamplingJob(pathSampler, dataChannel, newScale, canvasWidth, (long)(currentChunk.getX()));
			samplingThread.postJob(this, job);
// System.out.println ("Scheduled Job " + job);
		}

	}
}
